/* Symptom ticker and market map. Both are the interactive parts of the
   about page's argument, so they live next to it rather than in components/. */

/* Active symptom sits at the top, the next two follow below it. Pauses on
   hover so a reader who recognises themselves can stop and read it properly. */
function SymptomTicker({ data }) {
  const [i, setI] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const touch = React.useRef(null);
  const n = data.items.length;
  React.useEffect(() => {
    if (paused) return;
    const id = setInterval(() => setI((v) => (v + 1) % n), 5600);
    return () => clearInterval(id);
  }, [paused, n]);

  const at = (o) => data.items[(i + o + n) % n];
  const step = (d) => setI((v) => (v + d + n) % n);

  /* The neighbours are the control: click the line above or below to move to
     it. A 7px dot was a target, not an affordance. */
  const row = (text, active, o) => (
    <button onClick={active ? undefined : () => step(o)} disabled={active}
      style={{
        display: 'block', width: '100%', textAlign: 'left', border: 'none', background: 'transparent',
        padding: 0, cursor: active ? 'default' : 'pointer',
        fontFamily: 'var(--font-display)', fontWeight: active ? 500 : 400,
        fontSize: active ? 'clamp(20px, 2.4vw, 30px)' : 'clamp(15px, 1.6vw, 19px)',
        lineHeight: 1.3, letterSpacing: '-0.015em',
        color: active ? 'var(--black)' : 'var(--gray-500)',
        transition: 'font-size .5s cubic-bezier(.22,1,.36,1), color .5s ease',
      }}>{text}</button>
  );

  const arrow = (d, label) => (
    <button onClick={() => step(d)} aria-label={label}
      style={{ width: 44, height: 44, flexShrink: 0, border: 'none', cursor: 'pointer', background: 'transparent', color: 'var(--gray-500)', fontFamily: 'var(--font-mono)', fontSize: 13.5, lineHeight: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0 }}>
      {d < 0 ? '↑' : '↓'}
    </button>
  );

  return (
    <div onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}
      onTouchStart={(e) => { touch.current = { x: e.touches[0].clientX, y: e.touches[0].clientY }; setPaused(true); }}
      onTouchEnd={(e) => {
        const t = touch.current;
        if (t) {
          const dx = e.changedTouches[0].clientX - t.x;
          const dy = e.changedTouches[0].clientY - t.y;
          const d = Math.abs(dx) > Math.abs(dy) ? dx : dy;
          if (Math.abs(d) > 40) step(d < 0 ? 1 : -1);
        }
        touch.current = null;
        setPaused(false);
      }}
      style={{ touchAction: 'pan-y' }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14, minHeight: 150 }}>
        <div key={i} style={{ animation: 'sm-symptom-in .55s cubic-bezier(.22,1,.36,1) both' }}>{row(at(0), true, 0)}</div>
        {row(at(1), false, 1)}
        {row(at(2), false, 2)}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 24 }}>
        {arrow(-1, 'Previous symptom')}
        {arrow(1, 'Next symptom')}
        <span className="sm-mono" style={{ marginLeft: 10, fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', color: 'var(--gray-500)' }}>{String(i + 1).padStart(2, '0')} / {String(n).padStart(2, '0')}</span>
      </div>
      <p style={{ margin: '30px 0 0', maxWidth: '46ch', fontFamily: 'var(--font-display)', fontWeight: 500, fontSize: 'clamp(18px, 2vw, 23px)', lineHeight: 1.45, letterSpacing: '-0.015em', color: 'var(--black)' }}>{data.turn}</p>
    </div>
  );
}

/* Compact variant: map plus legend plus a small name+category readout, no
   per-country essay and no side panel. Used only where the copy has nothing
   country-specific to say (CS about page - most of the work there is under
   NDA or white-label, so there is no story to tell per market). */
/* Static variant: a compact map of Europe on the left, standing copy on the
   right. No metrics, no switches, no panel - the map says where the work
   happened and the text says what the work was like. All marked countries
   share ONE geographic gradient centred over Czechia, so the colour fades with
   distance from home; that is geography, not a rating, which is why there is
   no legend and no second category. Hover (pointer devices only) outlines a
   country and names it. Reads MARKETS from about-content.js. */
function MarketPanelMap({ data }) {
  const ref = React.useRef(null);
  const [hover, setHover] = React.useState(null);
  const [ready, setReady] = React.useState(false);
  const rows = window.MARKETS || [];
  const L = data.lang || 'Cs';
  const byId = React.useMemo(() => {
    const m = {};
    rows.forEach((e) => { m[e.id] = e; });
    return m;
  }, [rows]);
  const reduce = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const canHover = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;

  React.useEffect(() => {
    let dead = false;
    const draw = async () => {
      if (!window.d3 || !window.topojson || !ref.current) return;
      const topo = await d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json');
      if (dead || !ref.current) return;
      const all = topojson.feature(topo, topo.objects.countries).features;
      /* The frame drives the viewBox, so the box is exactly as tall as the fit
         and nothing is letterboxed. MultiPoint, not a Polygon: a hand-written
         ring is read by its winding order and d3 would fit the complement of
         it, nearly the globe. Edge midpoints are in because a conic projection
         bows the horizontal edges. */
      const frame = { type: 'MultiPoint', coordinates: [[-10.5, 35.6], [28, 35.6], [28, 60], [-10.5, 60], [9, 35], [9, 60.4]] };
      const W = 760, pad = 8, inner = W - pad * 2;
      const conic = () => d3.geoConicConformal().parallels([40, 57]).rotate([-11, 0]);
      const probe = conic().fitWidth(inner, frame);
      const pb = d3.geoPath(probe).bounds(frame);
      const H = Math.round(pb[1][1] - pb[0][1]) + pad * 2;
      const projection = conic().fitExtent([[pad, pad], [W - pad, H - pad]], frame);
      const path = d3.geoPath(projection);
      const svg = d3.select(ref.current).attr('viewBox', `0 0 ${W} ${H}`).attr('preserveAspectRatio', 'xMidYMid meet');
      svg.selectAll('*').remove();
      /* One radial wash anchored on Prague: deepest at home, lighter the
         further out the country sits. */
      const [cx, cy] = projection([14.42, 50.09]);
      const grad = svg.append('defs').append('radialGradient')
        .attr('id', 'sm-map-geo').attr('gradientUnits', 'userSpaceOnUse')
        .attr('cx', cx).attr('cy', cy).attr('r', W * 0.62);
      [['0%', '#5b2fb8'], ['28%', '#7d43cf'], ['58%', '#a878e0'], ['100%', '#dcc8ef']]
        .forEach(([o, c]) => grad.append('stop').attr('offset', o).attr('stop-color', c));
      /* Only geometry touching the window. Russia crosses the antimeridian, so
         its longitude bounds come back wrapped - judge those on latitude. */
      const countries = all.filter((f) => {
        const [[x0, y0], [x1, y1]] = d3.geoBounds(f);
        const wraps = x1 < x0;
        return (wraps || (x1 > -26 && x0 < 46)) && y1 > 31 && y0 < 64;
      });
      const sel = svg.append('g').selectAll('path').data(countries).join('path')
        .attr('d', path)
        .attr('data-id', (f) => f.id)
        .attr('fill', (f) => (byId[+f.id] ? 'url(#sm-map-geo)' : 'var(--gray-200)'))
        .attr('stroke', 'var(--paper)')
        .attr('stroke-width', 0.7)
        .style('transition', reduce ? 'none' : 'stroke .18s ease');
      if (canHover) {
        sel.filter((f) => !!byId[+f.id])
          .style('cursor', 'default')
          .on('mouseenter', (ev, f) => setHover(byId[+f.id]))
          .on('mouseleave', () => setHover(null));
      }
      setReady(true);
    };
    draw();
    return () => { dead = true; };
  }, [byId, reduce, canHover]);

  /* Outline painted from state so the hover cannot drift out of sync. */
  React.useEffect(() => {
    if (!ready || !ref.current) return;
    d3.select(ref.current).selectAll('path')
      .attr('stroke', function () {
        const e = byId[+this.getAttribute('data-id')];
        return e && hover && hover.id === e.id ? 'var(--orange-500)' : 'var(--paper)';
      })
      .attr('stroke-width', function () {
        const e = byId[+this.getAttribute('data-id')];
        return e && hover && hover.id === e.id ? 2 : 0.7;
      })
      .filter(function () { const e = byId[+this.getAttribute('data-id')]; return !!(e && hover && hover.id === e.id); })
      .raise();
  }, [hover, ready, byId]);

  const mono = { fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', textTransform: 'uppercase' };

  return (
    <div className="sm-market-grid" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 48fr) minmax(0, 52fr)', gap: 'clamp(22px, 4vw, 52px)', alignItems: 'start' }}>
      <div onMouseLeave={() => setHover(null)}>
        <svg ref={ref} className="sm-market-svg" style={{ width: '100%', maxWidth: 460, display: 'block', touchAction: 'pan-y' }} role="img" aria-label={data.mapLabel} />
        {!ready && <div style={{ ...mono, color: 'var(--gray-500)' }}>…</div>}
        {canHover && (
          <div style={{ minHeight: 28, marginTop: 10 }}>
            {hover && (
              <span style={{
                display: 'inline-flex', alignItems: 'center', padding: '5px 12px', borderRadius: 999,
                border: '1.5px solid var(--gray-300)', background: 'var(--paper)', color: 'var(--gray-700)',
                fontFamily: 'var(--font-meta)', fontWeight: 500, fontSize: 12.5, lineHeight: 1.2,
              }}>{hover['name' + L]}</span>
            )}
          </div>
        )}
      </div>
      <div>
        {(data.body || []).map((p, i) => (
          <p key={i} style={{ margin: i ? '16px 0 0' : 0, fontFamily: 'var(--font-body)', fontSize: 15.5, lineHeight: 1.68, color: 'var(--gray-600)', textWrap: 'pretty' }}>{p}</p>
        ))}
        {data.ndaNote && (
          <p style={{ margin: '22px 0 0', paddingTop: 16, borderTop: '1.5px solid var(--gray-200)', fontFamily: 'var(--font-body)', fontSize: 13, lineHeight: 1.6, color: 'var(--gray-500)', textWrap: 'pretty' }}>{data.ndaNote}</p>
        )}
      </div>
    </div>
  );
}

/* Europe rendered from Natural Earth geometry - never drawn by hand. */
function MarketMap({ data }) {
  if (data.panel) return <MarketPanelMap data={data} />;
  const ref = React.useRef(null);
  const [hover, setHover] = React.useState(null);
  const [ready, setReady] = React.useState(false);
  const byId = React.useMemo(() => {
    const m = {};
    data.entries.forEach((e) => { m[e.id] = e; });
    return m;
  }, [data.entries]);
  const reachSet = React.useMemo(() => new Set(data.reach || []), [data.reach]);

  React.useEffect(() => {
    let dead = false;
    const draw = async () => {
      if (!window.d3 || !window.topojson || !ref.current) return;
      const topo = await d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json');
      if (dead || !ref.current) return;
      const countries = topojson.feature(topo, topo.objects.countries).features;
      const W = 820, H = 620;
      const projection = d3.geoMercator().center([12, 53]).scale(600).translate([W / 2, H / 2]);
      const path = d3.geoPath(projection);
      const svg = d3.select(ref.current).attr('viewBox', `0 0 ${W} ${H}`).attr('preserveAspectRatio', 'xMidYMid meet');
      svg.selectAll('*').remove();
      /* One brand-gradient wash shared by all marked countries - the map is the
         section's artwork, so markets read as cut-outs of the hero gradient. */
      const grad = svg.append('defs').append('linearGradient')
        .attr('id', 'sm-map-grad').attr('gradientUnits', 'userSpaceOnUse')
        .attr('x1', W * 0.25).attr('y1', H * 0.15).attr('x2', W * 0.85).attr('y2', H * 0.9);
      [['0%', '#e64ba0'], ['30%', '#a83bd6'], ['55%', '#6b3fd6'], ['80%', '#3355d1'], ['100%', '#0f2f8a']].forEach(([o, c]) => grad.append('stop').attr('offset', o).attr('stop-color', c));
      svg.append('g').selectAll('path').data(countries).join('path')
        .attr('d', path)
        .attr('fill', (f) => (byId[+f.id] ? 'url(#sm-map-grad)' : reachSet.has(+f.id) ? 'var(--map-reach)' : 'var(--gray-200)'))
        .attr('stroke', 'var(--paper)')
        .attr('stroke-width', 0.8)
        .attr('data-id', (f) => f.id)
        .attr('tabindex', (f) => (byId[+f.id] ? 0 : null))
        .attr('role', (f) => (byId[+f.id] ? 'button' : null))
        .attr('aria-label', (f) => (byId[+f.id] ? `${byId[+f.id].name}: ${byId[+f.id].work}` : null))
        .style('cursor', (f) => (byId[+f.id] ? 'pointer' : 'default'))
        .style('transition', 'fill .25s ease')
        .on('mouseenter focus click', function (ev, f) {
          const e = byId[+f.id];
          if (!e) return;
          setHover(e);
          d3.select(this).attr('fill', 'var(--orange-500)');
        })
        .on('mouseleave blur', function (ev, f) {
          if (!byId[+f.id]) return;
          d3.select(this).attr('fill', 'url(#sm-map-grad)');
        });

      setReady(true);
    };
    draw();
    return () => { dead = true; };
  }, [byId, reachSet]);

  const mono = { fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: 9.5, letterSpacing: 'var(--tracking-label)', textTransform: 'uppercase' };
  const active = hover || data.entries[0];

  return (
    <div className="sm-about-map" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 320px', gap: 'clamp(24px, 4vw, 56px)', alignItems: 'stretch' }}>
      <div onMouseLeave={() => setHover(null)} style={{ minHeight: 380, display: 'flex', flexDirection: 'column' }}>
        <svg ref={ref} style={{ width: '100%', flex: '1 1 auto', minHeight: 0, display: 'block' }} role="img" aria-label={data.mapLabel} />
        {!ready && <div style={{ ...mono, color: 'var(--gray-500)' }}>Loading map…</div>}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 20, marginTop: 14 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span aria-hidden="true" style={{ width: 12, height: 12, borderRadius: 3, background: 'var(--gradient-hero)', display: 'block' }} />
            <span className="sm-mono" style={{ ...mono, color: 'var(--gray-500)' }}>{data.legendDeep}</span>
          </span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span aria-hidden="true" style={{ width: 12, height: 12, borderRadius: 3, background: 'var(--map-reach)', display: 'block' }} />
            <span className="sm-mono" style={{ ...mono, color: 'var(--gray-500)' }}>{data.legendReach}</span>
          </span>
        </div>
        {data.overseasNote && (
          <p style={{ margin: '14px 0 0', maxWidth: '56ch', fontFamily: 'var(--font-body)', fontWeight: 400, fontSize: 13.5, lineHeight: 1.6, color: 'var(--gray-500)', fontStyle: 'italic', textWrap: 'pretty' }}>{data.overseasNote}</p>
        )}
      </div>
      <div style={{ alignSelf: 'start', borderTop: '1.5px solid var(--gray-200)', paddingTop: 22 }}>
        <div className="sm-mono" style={{ ...mono, color: 'var(--gray-500)' }}>{data.mapLabel}</div>
        <div style={{ marginTop: 18, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 28, lineHeight: 1.1, letterSpacing: '-0.02em', color: 'var(--black)' }}>{active.name}</div>
        <p style={{ margin: '12px 0 0', minHeight: 100, fontFamily: 'var(--font-body)', fontSize: 15.5, lineHeight: 1.6, color: 'var(--gray-600)' }}>
          {active.work}
        </p>
      </div>
      <ul className="sm-market-list" style={{ gridColumn: '1 / -1', listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
        {data.entries.map((e) => {
          const on = active.id === e.id;
          return (
            <li key={e.id}>
              <button onClick={() => setHover(e)} onMouseEnter={() => setHover(e)} onFocus={() => setHover(e)}
                style={{
                  cursor: 'pointer', borderRadius: 999, padding: '8px 13px',
                  border: on ? '1.5px solid var(--black)' : '1.5px solid var(--gray-300)',
                  backgroundColor: on ? 'var(--black)' : 'transparent', color: on ? 'var(--white)' : 'var(--gray-600)',
                  fontFamily: 'var(--font-meta)', fontWeight: 500, fontSize: 13, lineHeight: 1.2,
                  transition: 'background-color .25s ease, border-color .25s ease',
                }}>
                {e.name}
                <span style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap' }}>: {e.work}</span>
              </button>
            </li>
          );
        })}
      </ul>
    </div>
  );
}

Object.assign(window, { SymptomTicker, MarketMap });
